#import "SFUEventSearchEngine.h"

// Singleton instance of SFUEventSearchEngine
static SFUEventSearchEngine* sharedSearchEngine = nil;
// Temporary collection of SFU events
static SFUEventCollection*   cachedEvents;

/**
 * An SFUEventSearchEngine is an object modelling a search engine
 * for all relevant events at SFU.
 * 
 * Contains the static list of parsable websites with updated
 * information on events at SFU. The SFUEventSearchEngine retains
 * a cached collection of these SFU events for matching with 
 * various search parameters.
 *
 * Results of the event search engine are handled by the event search
 * engine delegate. Thus the delegate must be set prior to any search
 * queries performed.
 *
 * Conforms to the SFUConnectionDelegate protocol.
 *
 * @author      James Lin
 * @version     0.6.5
 * @since       0.4.0
 * @see         SFUConnectionDelegate
 */
@implementation SFUEventSearchEngine

@synthesize searchKeywords;
@synthesize locationEventsOnly;
@synthesize silentSearch;

/**
 * Retrieve the shared instance of the SFU event search engine.
 *
 * The event cache is empty upon initialization of this shared instance.
 *
 * @return  the shared instance of SFUEventSearchEngine
 */
+ (SFUEventSearchEngine*)sharedSearchEngine {
	@synchronized(self) {
		if (sharedSearchEngine == nil) {
			sharedSearchEngine = [[SFUEventSearchEngine alloc] init];
			cachedEvents = [[SFUEventCollection alloc] init];
		}
		return sharedSearchEngine;
	}
}

/**
 * Allocate the SFU event search engine.
 *
 * @return  the newly allocated shared instance of SFUEventSearchEngine
 */
+ (id) alloc {
	@synchronized(self) {
		sharedSearchEngine = [super alloc];
		return sharedSearchEngine;
	}
}

/**
 * Retrieve a copy of the SFU event search engine.
 *
 * @return  the shared instance of SFUEventSearchEngine
 */
+ (id) copy {
	@synchronized(self) {
		return sharedSearchEngine;
	}
}

/**
 * Initialize the SFU event search engine.
 */
- (id)init {
	if (self = [super init]) {
		silentSearch = NO;
		locationEventsOnly = NO;
	}
	return self;
}

/**
 * Get the delegate handling search events.
 *
 * @return  the delegate handling search events
 * @see     SFUEventSearchEngineDelegate
 */
- (id<SFUEventSearchEngineDelegate>)delegate {
	return delegate;
}

/**
 * Set the delegate handling search events.
 *
 * @param theDelegate  the delegate handling search events
 * @see                SFUEventSearchEngineDelegate
 */
- (void)setDelegate:(id<SFUEventSearchEngineDelegate>)theDelegate {
	delegate = theDelegate;
}

/**
 * Search for all SFU events with their headings or categories 
 * containing any or all of the keywords in the given list of 
 * search keywords.
 *
 * Searching by any keyword in the list will return events sorted by
 * non-increasing order of number of keywords matched.
 *
 * An empty keyword list is valid and will search for all SFU events.
 * Enabling use of the cached events will result in an attempt to
 * search the the precached events. If the cache is empty, a full 
 * parse from website sources will be done prior to search.
 *
 * The results of the search will be passed to the delegate handling
 * search events. Thus this delegate must be set prior to the search 
 * query.
 *
 * @param keywords  the list of search keywords
 * @param flag      whether to use the event cache for the search
 * @param matchAll  YES if all keywords in the keyword list must match;
 *                  NO if any keyword can match.
 */
- (void)findEventsWithKeywords:(NSArray*)keywords 
					  useCache:(BOOL)flag 
					  matchAll:(BOOL)matchAll {
	// Create a search progress popup view
	progressView = [[[UIAlertView alloc] initWithTitle:@"\nSearching\nPlease Wait..." 
											   message:@"" 
											  delegate:self 
									 cancelButtonTitle:nil 
									 otherButtonTitles:nil] autorelease];
	
	// Create a progress indicator inside the progress popup
	progressIndicator = [[UIActivityIndicatorView alloc] 
						 initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
	progressIndicator.center = CGPointMake(140, 120);
	[progressView addSubview:progressIndicator];
	
	// Display the progress popup
	[progressIndicator startAnimating];
	[progressView show];
	
	// Cache the search keywords and matching option
	matchAllKeywords = matchAll;
	self.searchKeywords = keywords;
	
	if (!flag || [cachedEvents size] == 0) { // Initiate online search
		// Set initial website connection count
		webSourceCount = [[SFUConstants getCalendarURLs] count];
		
		// Set progress tracking variables
		expectedSize = 0.0;
		currentSize = 0.0;
		
		// Connect to the SFU calendar websites and retrieve source code
		for (NSString* url in [[SFUConstants getCalendarURLs] allValues]) {
			SFUCalendarSource* calendar = [[SFUCalendarSource alloc] 
										   initWithURLString:url 
													delegate:self];
			[calendar initConnection];
			[calendar autorelease];
		}
	} else { // Offline search through cache
		if ([searchKeywords count] > 0) { 
			// Search cached events via keyword
			[delegate searchDidFinish:nil 
							  results:[cachedEvents findEventsWithKeywords:searchKeywords 
														  matchAllKeywords:matchAllKeywords]];
		} else { 
			// Retrieve all cached events
			[delegate searchDidFinish:nil 
							  results:[cachedEvents getEvents]];
		}
	}
}

/**
 * Search for all SFU events with their headings or categories 
 * containing any or all of the keywords in the given list of 
 * search keywords.
 *
 * An empty keyword list is valid and will search for all SFU events.
 * Does not use cached events by default.
 *
 * The results of the search will be passed to the delegate handling
 * search events. Thus this delegate must be set prior to the search 
 * query.
 *
 * @param keywords  the list of search keywords
 * @param matchAll  YES if all keywords in the keyword list must match;
 *                  NO if any keyword can match.
 */
- (void)findEventsWithKeywords:(NSArray*)keywords 
			  matchAllKeywords:(BOOL)matchAll {
	[self findEventsWithKeywords:keywords 
						useCache:NO 
						matchAll:matchAll];
}

/**
 * Search for all SFU events with their headings or categories containing 
 * any or all of the keywords in the given space-delimited keyword string.
 *
 * @precondition    keyword string is not nil
 * @param keyword   the case-insensitive search keyword string
 * @param matchAll  YES if all keywords in the keyword list must match;
 *                  NO if any keyword can match.
 */
- (void)findEventsWithKeywordString:(NSString*)keyword
				   matchAllKeywords:(BOOL)matchAll {
	[self findEventsWithKeywords:[keyword componentsSeparatedByString:@" "]
				matchAllKeywords:matchAll];
}

/**
 * Handle a failed connection event to an SFU website.
 *
 * @param theConnection  the connection that just failed
 * @see                  SFUConnectionDelegate
 */
- (void)connectionDidFail:(NSURLConnection*)theConnection {
	webSourceCount--;
	
	// Keep track of number of website connnections
	if (webSourceCount == 0) {
		[delegate searchDidFail:theConnection 
					  withError:@"Search Failed\nUnable to connect to online sources"];
	}
}

/**
 * Handle a finished connection event to an SFU website.
 *
 * @param theConnection  the connection that just finished
 * @param results        an object of the results (can be nil)
 * @see                  SFUConnectionDelegate
 */
- (void)connectionDidFinish:(NSURLConnection*)theConnection 
					results:(id)results {
	webSourceCount--;
	
	// Parse the SFU calendar that just finished its load request
	SFUCalendarSource* calendar = (SFUCalendarSource*)results;
	for (SFUEvent* event in [calendar parseEvents]) {
		if (locationEventsOnly && [event.eventLocation length] == 0) {
			// Do not add the event
		} else {
			[cachedEvents addEvent:event];
		}
	}
	
	// Keep track of number of website connections
	if (webSourceCount == 0) {
		if ([self.searchKeywords count] > 0) { 
			// Search cached events via keywords
			[delegate searchDidFinish:theConnection 
							  results:[cachedEvents findEventsWithKeywords:self.searchKeywords 
														  matchAllKeywords:matchAllKeywords]];
			[progressView dismissWithClickedButtonIndex:0 animated:YES];
			[progressIndicator release];
		} else { 
			// Retrieve all cached events
			[delegate searchDidFinish:theConnection 
							  results:[cachedEvents getEvents]];
			[progressView dismissWithClickedButtonIndex:0 animated:YES];
			[progressIndicator release];
		}
	}
}

/**
 * Handle a received connection response event to an SFU website.
 *
 * @param theConnection  the connection that just responded
 * @param response       the response from the connection
 * @see                  SFUConnectionDelegate
 */
- (void)connection:(NSURLConnection*)theConnection
		didRespond:(NSURLResponse*)response {
	expectedSize += [[NSString stringWithFormat:@"%lli", 
					  [response expectedContentLength]] floatValue];
}

/**
 * Handle a received data event to an SFU website.
 *
 * @param theConnection  the connection that just finished
 * @param data           the partial data received from the connection
 * @see                  SFUConnectionDelegate
 */
- (void)connection:(NSURLConnection*)theConnection didReceiveData:(NSData*)data {
	currentSize += [[NSString stringWithFormat:@"%d", [data length]] floatValue];
	if ([delegate respondsToSelector:@selector(updateProgress:ofTotal:)]) {
		[delegate updateProgress:currentSize ofTotal:expectedSize];
	}
}

/**
 * Deallocate the SFU event search engine.
 */
- (void)dealloc {
	[searchKeywords release];
	[cachedEvents release];
	[sharedSearchEngine release];
	[super dealloc];
}
	
@end
